From de07c751193ce81e1bdf2aa24a6988c9fe66da55 Mon Sep 17 00:00:00 2001 From: "Karl O. Pinc" Date: Tue, 1 Dec 2020 17:21:08 -0600 Subject: [PATCH] Move view decorators into their own file --- src/pgwui_common/pgwui_common.py | 102 ---------- src/pgwui_common/view.py | 125 ++++++++++++ tests/test_pgwui_common.py | 272 +------------------------- tests/test_view.py | 326 +++++++++++++++++++++++++++++++ 4 files changed, 454 insertions(+), 371 deletions(-) create mode 100644 src/pgwui_common/view.py create mode 100644 tests/test_view.py diff --git a/src/pgwui_common/pgwui_common.py b/src/pgwui_common/pgwui_common.py index edbd819..45e9587 100644 --- a/src/pgwui_common/pgwui_common.py +++ b/src/pgwui_common/pgwui_common.py @@ -20,111 +20,9 @@ # Karl O. Pinc '''Configure supporting modules and other common elements -View decorators to expose useful variables to templates ''' import pgwui_common.views.page_views -from .plugin import find_pgwui_components -from . import exceptions as ex - - -def route_path(request, page_name, source): - '''Return the route path of the page's "source" - ''' - try: - return request.route_path(source) - except KeyError as old_ex: - raise ex.BadRouteError(page_name, old_ex) - - -def asset_path(request, page_name, source): - '''Return the static path to the asset's "source" - ''' - try: - return request.static_path(source) - except ValueError as old_ex: - raise ex.BadAssetError(page_name, old_ex) - - -def url_of_page(request, page_name): - '''Return a url to the page. This may or may not be fully - qualified, depending on what the user specifies in the settings. - ''' - page_conf = request.registry.settings['pgwui'][page_name] - type = page_conf['type'] - source = page_conf['source'] - if type == 'URL': - return source - if type == 'file': - return request.route_path(f'pgwui_common.{page_name}') - if type == 'route': - return route_path(request, page_name, source) - if type == 'asset': - return asset_path(request, page_name, source) - - -def set_menu_url(request, urls): - '''Add urls for pgwui_menu, return non-menu components - ''' - try: - menu_url = url_of_page(request, 'menu_page') - except KeyError: - try: - menu_url = request.route_path('pgwui_menu') - except KeyError: - return - if menu_url != urls['pgwui_home']: - urls['pgwui_menu'] = menu_url - - -def set_component_urls(request, urls): - '''Add urls for each pgwui component to the 'urls' dict - ''' - set_menu_url(request, urls) - components = find_pgwui_components() - if 'pgwui_menu' in components: - components.remove('pgwui_menu') - - for component in components: - try: - url = request.route_path(component) - except KeyError: - pass # In case a component has no route - else: - urls.setdefault(component, url) - - -def set_urls(request, urls): - '''Build 'urls' dict with all the urls - ''' - home_url = url_of_page(request, 'home_page') - urls.setdefault('pgwui_home', home_url) - set_component_urls(request, urls) - - -def base_view(wrapped): - '''Decorator for any view which includes base.mk. - ''' - def wrapper(request): - '''Add variables missing but needed by base.mk to the response. - ''' - response = wrapped(request) - pgwui = response.get('pgwui', {}) - urls = pgwui.setdefault('urls', dict()) - set_urls(request, urls) - response['pgwui'] = pgwui - return response - return wrapper - - -def auth_base_view(wrapped): - '''Decorator for any view which includes auth_base.mk. - ''' - def wrapper(request): - '''Add variables needed by auth_base.mk to the response. - ''' - return base_view(wrapped)(request) - return wrapper def configure_page(config, pgwui_settings, page_name): diff --git a/src/pgwui_common/view.py b/src/pgwui_common/view.py new file mode 100644 index 0000000..59fb649 --- /dev/null +++ b/src/pgwui_common/view.py @@ -0,0 +1,125 @@ +# Copyright (C) 2018, 2020 The Meme Factory, Inc. http://www.karlpinc.com/ + +# This file is part of PGWUI_Common. +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# . +# + +# Karl O. Pinc + +'''View decorators to expose useful response "variables" to templates +''' + +from .plugin import find_pgwui_components +from . import exceptions as ex + + +def route_path(request, page_name, source): + '''Return the route path of the page's "source" + ''' + try: + return request.route_path(source) + except KeyError as old_ex: + raise ex.BadRouteError(page_name, old_ex) + + +def asset_path(request, page_name, source): + '''Return the static path to the asset's "source" + ''' + try: + return request.static_path(source) + except ValueError as old_ex: + raise ex.BadAssetError(page_name, old_ex) + + +def url_of_page(request, page_name): + '''Return a url to the page. This may or may not be fully + qualified, depending on what the user specifies in the settings. + ''' + page_conf = request.registry.settings['pgwui'][page_name] + type = page_conf['type'] + source = page_conf['source'] + if type == 'URL': + return source + if type == 'file': + return request.route_path(f'pgwui_common.{page_name}') + if type == 'route': + return route_path(request, page_name, source) + if type == 'asset': + return asset_path(request, page_name, source) + + +def set_menu_url(request, urls): + '''Add urls for pgwui_menu, return non-menu components + ''' + try: + menu_url = url_of_page(request, 'menu_page') + except KeyError: + try: + menu_url = request.route_path('pgwui_menu') + except KeyError: + return + if menu_url != urls['pgwui_home']: + urls['pgwui_menu'] = menu_url + + +def set_component_urls(request, urls): + '''Add urls for each pgwui component to the 'urls' dict + ''' + set_menu_url(request, urls) + components = find_pgwui_components() + if 'pgwui_menu' in components: + components.remove('pgwui_menu') + + for component in components: + try: + url = request.route_path(component) + except KeyError: + pass # In case a component has no route + else: + urls.setdefault(component, url) + + +def set_urls(request, urls): + '''Build 'urls' dict with all the urls + ''' + home_url = url_of_page(request, 'home_page') + urls.setdefault('pgwui_home', home_url) + set_component_urls(request, urls) + + +def base_view(wrapped): + '''Decorator for any view which includes base.mk. + ''' + def wrapper(request): + '''Add variables missing but needed by base.mk to the response. + ''' + response = wrapped(request) + pgwui = response.get('pgwui', {}) + urls = pgwui.setdefault('urls', dict()) + set_urls(request, urls) + response['pgwui'] = pgwui + return response + return wrapper + + +def auth_base_view(wrapped): + '''Decorator for any view which includes auth_base.mk. + ''' + def wrapper(request): + '''Add variables needed by auth_base.mk to the response. + ''' + return base_view(wrapped)(request) + return wrapper diff --git a/tests/test_pgwui_common.py b/tests/test_pgwui_common.py index ab95852..dbd14cf 100644 --- a/tests/test_pgwui_common.py +++ b/tests/test_pgwui_common.py @@ -24,8 +24,8 @@ import pyramid.config import pyramid.testing from pyramid.threadlocal import get_current_request +import pgwui_common.view as view import pgwui_common.pgwui_common as pgwui_common -import pgwui_common.exceptions as common_ex from pgwui_testing import testing @@ -38,7 +38,7 @@ pytest_plugins = ("pgwui",) FOO_URL = 'foo://bar/' mock_find_pgwui_components = testing.make_mock_fixture( - pgwui_common, 'find_pgwui_components') + view, 'find_pgwui_components') mock_method_route_path = testing.instance_method_mock_fixture('route_path') mock_route_url = testing.instance_method_mock_fixture('route_url') @@ -62,272 +62,6 @@ def check_base_view_results(request, pgwui): # Unit tests -# route_path() - -@pytest.mark.unittest -def test_route_path_with_path(pyramid_request_config, mock_method_route_path): - '''static_path() result is returned - ''' - expected = 'route' - - request = get_current_request() - mocked_route_path = mock_method_route_path(request) - mocked_route_path.return_value = expected - - result = pgwui_common.route_path(request, None, None) - - assert result == expected - - -@pytest.mark.unittest -def test_route_path_no_path(pyramid_request_config, mock_method_route_path): - '''BadRouteError() raised when there's no path - ''' - request = get_current_request() - mocked_route_path = mock_method_route_path(request) - mocked_route_path.side_effect = KeyError - - with pytest.raises(common_ex.BadRouteError): - pgwui_common.route_path(request, None, None) - - assert True - - -mock_route_path = testing.make_mock_fixture( - pgwui_common, 'route_path') - - -# asset_path() - -@pytest.mark.unittest -def test_asset_path_with_path(pyramid_request_config, mock_static_path): - '''static_path() result is returned - ''' - expected = 'static' - - request = get_current_request() - mocked_static_path = mock_static_path(request) - mocked_static_path.return_value = expected - - result = pgwui_common.asset_path(request, None, None) - - assert result == expected - - -@pytest.mark.unittest -def test_asset_path_no_path(pyramid_request_config, mock_static_path): - '''BadAssetError() raised when there's no path - ''' - request = get_current_request() - mocked_static_path = mock_static_path(request) - mocked_static_path.side_effect = ValueError - - with pytest.raises(common_ex.BadAssetError): - pgwui_common.asset_path(request, None, None) - - assert True - - -mock_asset_path = testing.make_mock_fixture( - pgwui_common, 'asset_path') - - -# url_of_page() - -@pytest.mark.parametrize( - ('pgwui', 'page_name', 'expected'), [ - ({'test_page': {'type': 'URL', - 'source': 'somesource'}}, - 'test_page', - 'somesource'), - ({'test_page': {'type': 'file', - 'source': 'somesource'}}, - 'test_page', - 'pgwui_common.test_page'), - ({'test_page': {'type': 'route', - 'source': 'somesource'}}, - 'test_page', - 'routepath'), - ({'test_page': {'type': 'asset', - 'source': 'somesource'}}, - 'test_page', - 'static'), - ({'test_page': {'type': 'impossible', - 'source': 'somesource'}}, - 'test_page', - None)]) -@pytest.mark.unittest -def test_url_of_page( - pyramid_request_config, mock_method_route_path, - mock_route_path, mock_asset_path, pgwui, page_name, expected): - '''The right results and calls are made - ''' - mock_asset_path.return_value = 'static' - mock_route_path.return_value = 'routepath' - - request = get_current_request() - mocked_route_path = mock_method_route_path(request) - mocked_route_path.side_effect = lambda x: x - - request.registry.settings['pgwui'] = pgwui - result = pgwui_common.url_of_page(request, page_name) - - assert result == expected - - -mock_url_of_page = testing.make_mock_fixture( - pgwui_common, 'url_of_page') - - -# set_menu_url() - -@pytest.mark.unittest -@pytest.mark.parametrize( - "test_urls,expected", - [ - # menu and home have identical urls, no url is added for menu - ({'pgwui_menu': '/', 'pgwui_home': '/'}, - {}), - # No menu url, no url is added for menu - ({'pgwui_home': '/'}, - {}), - # menu and home have different urls, url is added for menu - ({'pgwui_menu': '/menu', 'pgwui_home': '/'}, - {'pgwui_menu': '/menu'})]) -def test_set_menu_url( - pyramid_request_config, mock_method_route_path, mock_url_of_page, - test_urls, expected): - '''The expected urls are returned - ''' - def path_func(name): - return test_urls[name] - - mock_url_of_page.side_effect = lambda *args: test_urls['pgwui_menu'] - request = get_current_request() - mocked_route_path = mock_method_route_path(request) - mocked_route_path.side_effect = path_func - - urls = {'pgwui_home': test_urls['pgwui_home']} - expected.update(urls) - pgwui_common.set_menu_url(request, urls) - - assert urls == expected - - -mock_set_menu_url = testing.make_mock_fixture( - pgwui_common, 'set_menu_url') - - -# set_component_urls() - -@pytest.mark.parametrize( - 'test_urls', [ - # With a pgwui_menu - {'pgwui_menu': '/menu', - 'pgwui_logout': '/logout', - 'pgwui_foo': '/foo', - 'pgwui_home': '/'}, - # Without a pgwui_menu - {'pgwui_logout': '/logout', - 'pgwui_foo': '/foo', - 'pgwui_home': '/'}]) -@pytest.mark.unittest -def test_set_component_urls( - pyramid_request_config, mock_method_route_path, mock_set_menu_url, - mock_find_pgwui_components, test_urls): - '''Urls are set for every component which has a route, except for - pgwui_menu - ''' - test_components = list(test_urls) + ['pgwui_noroute'] - - def url_func(url): - return test_urls[url] - - request = get_current_request() - mocked_route_path = mock_method_route_path(request) - mocked_route_path.side_effect = url_func - mock_find_pgwui_components.return_value = test_components - - urls = dict() - pgwui_common.set_component_urls(request, urls) - - expected_urls = test_urls.copy() - if 'pgwui_menu' in expected_urls: - del expected_urls['pgwui_menu'] - - mock_set_menu_url.assert_called_once() - assert urls == expected_urls - - -mock_set_component_urls = testing.make_mock_fixture( - pgwui_common, 'set_component_urls') - - -# set_urls() - -@pytest.mark.unittest -def test_set_urls( - pyramid_request_config, mock_url_of_page, mock_set_component_urls): - '''The 'home' url is added and set_component_urls() called - ''' - test_home_route = '/' - request = get_current_request() - - mock_url_of_page.return_value = test_home_route - - urls = dict() - pgwui_common.set_urls(request, urls) - - assert urls['pgwui_home'] == test_home_route - mock_set_component_urls.assert_called_once() - - -mock_set_urls = testing.make_mock_fixture( - pgwui_common, 'set_urls') - - -# base_view() -@pytest.mark.unittest -def test_base_view_urls(mock_set_urls): - '''The response has the 'pgwui['urls']' dict added to it''' - def mock_view(request): - return {} - - wrapper = pgwui_common.base_view(mock_view) - response = wrapper(get_current_request()) - - assert 'pgwui' in response - pgwui = response['pgwui'] - assert 'urls' in pgwui - assert isinstance(pgwui['urls'], dict) - - -@pytest.mark.unittest -def test_base_view_default(mock_set_urls): - '''The response retains the mock view's variables''' - wrapper = pgwui_common.base_view(mock_view) - request = get_current_request() - response = wrapper(request) - pgwui = response['pgwui'] - check_base_view_results(request, pgwui) - - -mock_base_view = testing.make_mock_fixture(pgwui_common, 'base_view') - - -# auth_base_view() - -@pytest.mark.unittest -def test_auth_base_view(mock_base_view): - '''Wrapper calls base_view() - ''' - wrapper = pgwui_common.auth_base_view(mock_view) - request = get_current_request() - wrapper(request) - - mock_base_view.assert_called_once() - - # configure_page() @pytest.mark.unittest @@ -422,7 +156,7 @@ def test_auth_base_view_integration( for name, url in test_urls.items(): pyramid_request_config.add_route(name, url) - wrapper = pgwui_common.auth_base_view(mock_view) + wrapper = view.auth_base_view(mock_view) request = get_current_request() result = wrapper(request) diff --git a/tests/test_view.py b/tests/test_view.py new file mode 100644 index 0000000..42291dc --- /dev/null +++ b/tests/test_view.py @@ -0,0 +1,326 @@ +# Copyright (C) 2018, 2020 The Meme Factory, Inc. http://www.karlpinc.com/ + +# This file is part of PGWUI_Common. +# +# This program is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License +# as published by the Free Software Foundation, either version 3 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# . +# + +# Karl O. Pinc + +import pytest +from pyramid.threadlocal import get_current_request + +import pgwui_common.view as view +import pgwui_common.exceptions as common_ex + +from pgwui_testing import testing + +# Activiate our pytest plugin +pytest_plugins = ("pgwui",) + + +# Helper functions and constants + +FOO_URL = 'foo://bar/' + +mock_find_pgwui_components = testing.make_mock_fixture( + view, 'find_pgwui_components') + +mock_method_route_path = testing.instance_method_mock_fixture('route_path') +mock_route_url = testing.instance_method_mock_fixture('route_url') +mock_include = testing.instance_method_mock_fixture('include') +mock_add_static_view = testing.instance_method_mock_fixture('add_static_view') +mock_add_route = testing.instance_method_mock_fixture('add_route') +mock_add_view = testing.instance_method_mock_fixture('add_view') +mock_static_path = testing.instance_method_mock_fixture('static_path') + + +def mock_view(request): + if (hasattr(request, 'registry') + and 'pgwui' in request.registry.settings): + return request.registry.settings + return {'pgwui': {'foo': FOO_URL}} + + +def check_base_view_results(request, pgwui): + assert pgwui['foo'] == FOO_URL + + +# Unit tests + +# route_path() + +@pytest.mark.unittest +def test_route_path_with_path(pyramid_request_config, mock_method_route_path): + '''static_path() result is returned + ''' + expected = 'route' + + request = get_current_request() + mocked_route_path = mock_method_route_path(request) + mocked_route_path.return_value = expected + + result = view.route_path(request, None, None) + + assert result == expected + + +@pytest.mark.unittest +def test_route_path_no_path(pyramid_request_config, mock_method_route_path): + '''BadRouteError() raised when there's no path + ''' + request = get_current_request() + mocked_route_path = mock_method_route_path(request) + mocked_route_path.side_effect = KeyError + + with pytest.raises(common_ex.BadRouteError): + view.route_path(request, None, None) + + assert True + + +mock_route_path = testing.make_mock_fixture( + view, 'route_path') + + +# asset_path() + +@pytest.mark.unittest +def test_asset_path_with_path(pyramid_request_config, mock_static_path): + '''static_path() result is returned + ''' + expected = 'static' + + request = get_current_request() + mocked_static_path = mock_static_path(request) + mocked_static_path.return_value = expected + + result = view.asset_path(request, None, None) + + assert result == expected + + +@pytest.mark.unittest +def test_asset_path_no_path(pyramid_request_config, mock_static_path): + '''BadAssetError() raised when there's no path + ''' + request = get_current_request() + mocked_static_path = mock_static_path(request) + mocked_static_path.side_effect = ValueError + + with pytest.raises(common_ex.BadAssetError): + view.asset_path(request, None, None) + + assert True + + +mock_asset_path = testing.make_mock_fixture( + view, 'asset_path') + + +# url_of_page() + +@pytest.mark.parametrize( + ('pgwui', 'page_name', 'expected'), [ + ({'test_page': {'type': 'URL', + 'source': 'somesource'}}, + 'test_page', + 'somesource'), + ({'test_page': {'type': 'file', + 'source': 'somesource'}}, + 'test_page', + 'pgwui_common.test_page'), + ({'test_page': {'type': 'route', + 'source': 'somesource'}}, + 'test_page', + 'routepath'), + ({'test_page': {'type': 'asset', + 'source': 'somesource'}}, + 'test_page', + 'static'), + ({'test_page': {'type': 'impossible', + 'source': 'somesource'}}, + 'test_page', + None)]) +@pytest.mark.unittest +def test_url_of_page( + pyramid_request_config, mock_method_route_path, + mock_route_path, mock_asset_path, pgwui, page_name, expected): + '''The right results and calls are made + ''' + mock_asset_path.return_value = 'static' + mock_route_path.return_value = 'routepath' + + request = get_current_request() + mocked_route_path = mock_method_route_path(request) + mocked_route_path.side_effect = lambda x: x + + request.registry.settings['pgwui'] = pgwui + result = view.url_of_page(request, page_name) + + assert result == expected + + +mock_url_of_page = testing.make_mock_fixture( + view, 'url_of_page') + + +# set_menu_url() + +@pytest.mark.unittest +@pytest.mark.parametrize( + "test_urls,expected", + [ + # menu and home have identical urls, no url is added for menu + ({'pgwui_menu': '/', 'pgwui_home': '/'}, + {}), + # No menu url, no url is added for menu + ({'pgwui_home': '/'}, + {}), + # menu and home have different urls, url is added for menu + ({'pgwui_menu': '/menu', 'pgwui_home': '/'}, + {'pgwui_menu': '/menu'})]) +def test_set_menu_url( + pyramid_request_config, mock_method_route_path, mock_url_of_page, + test_urls, expected): + '''The expected urls are returned + ''' + def path_func(name): + return test_urls[name] + + mock_url_of_page.side_effect = lambda *args: test_urls['pgwui_menu'] + request = get_current_request() + mocked_route_path = mock_method_route_path(request) + mocked_route_path.side_effect = path_func + + urls = {'pgwui_home': test_urls['pgwui_home']} + expected.update(urls) + view.set_menu_url(request, urls) + + assert urls == expected + + +mock_set_menu_url = testing.make_mock_fixture( + view, 'set_menu_url') + + +# set_component_urls() + +@pytest.mark.parametrize( + 'test_urls', [ + # With a pgwui_menu + {'pgwui_menu': '/menu', + 'pgwui_logout': '/logout', + 'pgwui_foo': '/foo', + 'pgwui_home': '/'}, + # Without a pgwui_menu + {'pgwui_logout': '/logout', + 'pgwui_foo': '/foo', + 'pgwui_home': '/'}]) +@pytest.mark.unittest +def test_set_component_urls( + pyramid_request_config, mock_method_route_path, mock_set_menu_url, + mock_find_pgwui_components, test_urls): + '''Urls are set for every component which has a route, except for + pgwui_menu + ''' + test_components = list(test_urls) + ['pgwui_noroute'] + + def url_func(url): + return test_urls[url] + + request = get_current_request() + mocked_route_path = mock_method_route_path(request) + mocked_route_path.side_effect = url_func + mock_find_pgwui_components.return_value = test_components + + urls = dict() + view.set_component_urls(request, urls) + + expected_urls = test_urls.copy() + if 'pgwui_menu' in expected_urls: + del expected_urls['pgwui_menu'] + + mock_set_menu_url.assert_called_once() + assert urls == expected_urls + + +mock_set_component_urls = testing.make_mock_fixture( + view, 'set_component_urls') + + +# set_urls() + +@pytest.mark.unittest +def test_set_urls( + pyramid_request_config, mock_url_of_page, mock_set_component_urls): + '''The 'home' url is added and set_component_urls() called + ''' + test_home_route = '/' + request = get_current_request() + + mock_url_of_page.return_value = test_home_route + + urls = dict() + view.set_urls(request, urls) + + assert urls['pgwui_home'] == test_home_route + mock_set_component_urls.assert_called_once() + + +mock_set_urls = testing.make_mock_fixture( + view, 'set_urls') + + +# base_view() +@pytest.mark.unittest +def test_base_view_urls(mock_set_urls): + '''The response has the 'pgwui['urls']' dict added to it''' + def mock_view(request): + return {} + + wrapper = view.base_view(mock_view) + response = wrapper(get_current_request()) + + assert 'pgwui' in response + pgwui = response['pgwui'] + assert 'urls' in pgwui + assert isinstance(pgwui['urls'], dict) + + +@pytest.mark.unittest +def test_base_view_default(mock_set_urls): + '''The response retains the mock view's variables''' + wrapper = view.base_view(mock_view) + request = get_current_request() + response = wrapper(request) + pgwui = response['pgwui'] + check_base_view_results(request, pgwui) + + +mock_base_view = testing.make_mock_fixture(view, 'base_view') + + +# auth_base_view() + +@pytest.mark.unittest +def test_auth_base_view(mock_base_view): + '''Wrapper calls base_view() + ''' + wrapper = view.auth_base_view(mock_view) + request = get_current_request() + wrapper(request) + + mock_base_view.assert_called_once() -- 2.34.1